#!/bin/bash
# 根据用户输入参数快速生成Java Web应用服务框架的命令行脚本程序
sh_folder="$(cd "$(dirname $0)"; pwd -P)"
# Default case for Linux sed, just use "-i"
sedi=(-i)
case "$(uname)" in
  # For macOS, use two parameters
  Darwin*) sedi=(-i "")
esac
# 命令行读取用户输入的参数并验证用户输入的值的有效性
# 如果输入无效则报错提示用户继续输入，直到用户输入的值为有效为止
# $1 保存参数默认值的变量名(用于间接引用)
# $2 验证参数有效性的正则表达式
# $3 参数无效时的错误提示信息
# $4 数字参数最大值<
# $5 数字参数最小值>
# 返回值保存在$1指定的变量中
function read_valid_arg()
{
    # 输入的变量名
    local varname=$1
    #echo varname=$varname
    # 输入的变量名的值
    local defvalue=${!varname}
    #[ -n "$4" ] && echo \$4=$4
    #[ -n "$5" ] && echo \$5=$5
    # macOS read 不支持 -i 参数
    if [ "$(uname -s)" == Darwin ]
    then
        read -e  -p "Enter使用默认值[$defvalue]:" input
        local value="${input:-$defvalue}"
        local isvalid=0
        while true 
        do
            [[ ! "$value" =~ $2 ]] && isvalid=1 
            # 判断数字是否在指定区间内区间
            [ -n "$4" ] && [ $value -ge $4 ] && isvalid=1 && echo 超过最大值 $4
            [ -n "$5" ] && [ $value -le $5 ] && isvalid=1 && echo 小于最小值 $5
            
            if [ $isvalid -eq 0 ] 
            then
                break; 
            else
                isvalid=0
                echo validate regex=$2
                read -e -p "$3,Enter使用默认值[$defvalue]:" input
                value="${input:-$1}"
            fi
        done        
    else
        read -e -i "$defvalue" -p "Enter使用默认值:" input
        local value="${input:-$defvalue}"
        local isvalid=0
        while true 
        do
            [[ ! "$value" =~ $2 ]] && isvalid=1 
            # 判断数字是否在指定区间内区间
            [ -n "$4" ] && [ $value -ge $4 ] && isvalid=1 && echo 超过最大值 $4
            [ -n "$5" ] && [ $value -le $5 ] && isvalid=1 && echo 小于最小值 $5
            
            if [ $isvalid -eq 0 ] 
            then
                break; 
            else
                isvalid=0
                echo validate regex=$2
                read -e -i "$defvalue" -p "$3:" input
                value="${input:-$1}"
            fi
        done
    fi 
    echo value=$value
    # 将验证有效的值赋值给变量
    eval "$varname=$value"
}
# 修改$1指定的文件名中名为$2的值为$3
# $1 key=value格式保存变量值的文件名
# $2 变量名key
# $3 变量的值value
# $4 sudo prefix
# 修改成功返回0, 失败退出脚本,$1 文件不存在则创建文件并添加key=value
function set_value(){
    if [ ! -f "$1" ]
    then 
        # 添加key=value对到文件
        [ ! -d "$(dirname $1)" ] && mkdir -p "$(dirname $1)"
        $4 echo -e "\n$2=$3" >> $1 || exit
        return 0
    fi
    if $4 grep -q -E "^\s*$2\s*=.*$" $1
    then
        local r="$3"
        r=${r//&/\\&}
        r=${r//!/\\!}
        r=${r//\//\\/}
        # 如果存在key=value定义，则修改定义,否则向文件添加定义
        $4 sed "${sedi[@]}"  -r "s/(^\s*$2\s*=).*$/\\1$r/g" $1 || exit
        return 0
    fi
    if $4 grep -q -E "^(#+\s*)+$2\s*=.*$" $1
    then
        local r="$3"
        r=${r//&/\\&}
        r=${r//!/\\!}
        # 如果存在#开头的key=value(无效)定义,添加key=value对到找到行之后
        $4 sed "${sedi[@]}"  -r "/^(#+\s*)+$2\s*=.*$/a\\$2=$r" "$1" || exit
        return 0
    fi
    # 添加key=value对到文件
    $4 bash -c "echo ;echo $2=$3 >> $1" || exit
    return 0  
}

# 查找xml文件中指定tag的起始和结束标志
# 执行成功 tag_begin 保存起始标志行号,tag_end 保存结束标志行号
# $1 xml file
# $2 tag name, tag为.分割节点的字符串,如 database.jdbc
# 正常执行返回0,
# $1 不存在,$2为空返回255
# 有多个相同节点,没找到节点则失败返回255
function find_xml_tags()
{
    find_xml_tag_begin=
    find_xml_tag_end=
    if [ -f "$1" ] && [ -n "$2" ]
    then
        # 将.分割的节点名转为数组
        local array=(${2//./ })
        local size=${#array[@]}
        #echo size=$size
        if [ $size -ge 1 ]
        then
            local tag=${array[0]}            
            [ -z "$tag" ] && return 255
            # 查找第一个节点
            tag_begin=$(sed -n -r  "/<\s*$tag/=" "$1" )  && \
            tag_end=$(sed -n -r  "/<\/\s*$tag\s*>/=" "$1" )
            local ab=($tag_begin)
            local ae=($tag_end)
            # 找到标记数量不是1则失败返回
            if [ ${#ab[@]}  -ne 1 ] || [ ${#ae[@]}  -ne 1 ] ; then return 255 ; fi
            #echo $tag tag_begin=$tag_begin tag_end=$tag_end
            # 根据第一个顶级节点给定的行号范围循环查找所有其他子节点
            # 以后的每次循环都在上次找到的行号范围内查找,会一步步缩小范围
            for (( i = 1 ; i < $size ; i++ ))
            do
                tag=${array[i]}
                [ -z "$tag" ] && return 255
                # 在$tag_begin,tag_end给定范围的值内查找
                local b=$(sed -n "$tag_begin,${tag_end}p" "$1" | sed -n -r "/<\s*$tag/=")
                local e=$(sed -n "$tag_begin,${tag_end}p" "$1" | sed -n -r "/<\/\s*$tag\s*>/=")
                local ab=($b)
                local ae=($e)
                # 找到标记数量不是1则失败返回
                if [ ${#ab[@]}  -ne 1 ] || [ ${#ae[@]}  -ne 1 ] ; then return 255 ; fi
                #echo b=$b e=$e
                # b,e都是相对位置,在这里要转换为整文件的行号
                let "tag_end=$tag_begin+$e-1"
                let "tag_begin=$tag_begin+$b-1"
                #echo $tag  tag_begin=$tag_begin tag_end=$tag_end
            done
            return 0 
        fi
    fi
    return 255
}
# 设置xml文件中指定property的值,
# $1 xml file
# $2 .分割的节点的字符串,如 database.jdbc
# $3 value 
# 正常执行返回0
# $1 不存在,$2为空返回255
# 有多个相同节点,没找到节点则失败返回255
# sed 修改文件失败返回sed错误代码
function set_xml_value()
{
    find_xml_tags "$1" "$2" || exit
    local last=${2##*.}
    sed "${sedi[@]}" -r "$tag_begin,${tag_end}s!(<\s*$last.*>).*(</\s*$last\s*>)!\1$3\2!1" "$1" || exit
}

#set_xml_value $sh_folder/template/myrpc-local/src/main/resources/defaultConfig.xml server.start false
# find_xml_tags $sh_folder/template/myrpc-local/src/main/resources/defaultConfig.xml database.jdbc.schema && \
#     echo tag_begin=$tag_begin tag_end=$tag_end && \
#     sed -n "$tag_begin,${tag_end}p" $sh_folder/template/myrpc-local/src/main/resources/defaultConfig.xml

# sed -i -r "$tag_begin,${tag_end}s!(<\s*schema.*>).*(</\s*schema\s*>)!\1hello\2!1" $sh_folder/template/myrpc-local/src/main/resources/defaultConfig.xml
#exit

# 检查输入的字符串是否代表true含义
# $1
function istrue()
{
	[[ "$1" =~ ^(true|y(es)?|on|1)$ ]]
}
# 检查输入的字符串是否代表true含义
# $1
function notrue()
{
	[[ ! "$1" =~ ^(true|y(es)?|on|1)$ ]]
}
# 解析输入的URL返回域名部分
# 如果输入参数不是URL,返回空
# $1 URL
function host_of_url()
{
	[[ "$1" =~ ^https?://([[:alnum:]\.]+) ]] && echo ${BASH_REMATCH[1]}
}

pushd "$sh_folder"
status_f="$sh_folder/rpcgen_status"
############################################################
# 创建初始的用户配置                                       #
# rpcgen_status 为保存用户配置的文件,                      #
# 如果不存在则从 rpcgen_status.init 复制一份               #
############################################################
[ -f "$sh_folder/rpcgen_status" ] || cp "$status_f.init" "$status_f" || exit
source "$status_f" || exit
############################################################
#                 提示用户输入项目配置参数                 #
############################################################
grep -B6 'start=' "$status_f" | head -n 6
#########################projectname########################
[ -z "$projectname" ] && projectname=hello-world
grep -B7 'projectname=' "$status_f" | head -n 7
read_valid_arg projectname "$projectname_rx" "无效的项目名称,请重新输入"
echo projectname=$projectname

###########################package##########################
[ -z "$package" ] && package=com.mycompany
grep -B7 'package=' "$status_f" | head -n 7
read_valid_arg package "$package_rx" "无效的包名,请重新输入"
echo package=$package

###########################prj_package##########################
[ -z "$prj_package" ] && prj_package=$package.${projectname//-/_}
grep -B4 'prj_package=' "$status_f" | head -n 4
read_valid_arg prj_package "$prj_package_rx" "无效的包名,请重新输入"
echo prj_package=$prj_package

###########################service##########################
[ -z "$service" ] && service=$(to_camel_case "${projectname}")
grep -B8 'service=' "$status_f" | head -n 8
read_valid_arg service "$service_rx" "无效的类名,请重新输入"
echo service=$service

###########################groupid##########################
[ -z "$groupid" ] && groupid=$package
grep -B8 'groupid=' "$status_f" | head -n 8
read_valid_arg groupid "$groupid_rx" "无效的groupId,请重新输入"
echo groupid=$groupid

###########################artifactid#######################
[ -z "$artifactid" ] && artifactid=$projectname
grep -B8 'artifactid=' "$status_f" | head -n 8
read_valid_arg artifactid "$artifactid_rx" "无效的artifactId,请重新输入"
echo artifactid=$artifactid
###########################version##########################
[ -z "$version" ] && version=0.0.0-SNAPSHOT
grep -B5 'version=' "$status_f" | head -n 5
read_valid_arg version "$version_rx" "无效的版本号,请重新输入"
echo version=$version
###########################prjurl##########################
# 将 . 分割的package字符串转换为域名
myhost=$(echo "$package" | perl -lne 'print join ".", reverse split/\./;')
location=$myhost/$projectname
[ -z "$prjurl" ] && prjurl="https://$location"
grep -B6 'prjurl=' "$status_f" | head -n 6
read_valid_arg prjurl "$prjurl_rx" "无效的URL,请重新输入"
echo prjurl=$prjurl
###########################scmurl##########################
[[ "$prjurl" =~  ^[a-zA-Z0-9_-]+://(.+)$ ]] && location=${BASH_REMATCH[1]}
[ -z "$scmurl" ] && scmurl="scm:git:https://$location.git"
grep -B6 'scmurl=' "$status_f" | head -n 6
read_valid_arg scmurl "$scmurl_rx" "无效的URL,请重新输入"
echo scmurl=$scmurl
###########################author##########################
[ -z "$author" ] && author=unknow_author
grep -B6 'author=' "$status_f" | head -n 6
read_valid_arg author "$author_rx" "无效的作者名,请重新输入"
echo author=$author
###########################email###########################
[ -z "$email" ] && email=$author@$myhost
grep -B4 'email=' "$status_f" | head -n 4
read_valid_arg email "$email_rx" "无效的email,请重新输入"
echo email=$email
###########################homepage###########################
[ -z "$homepage" ] && homepage=http://$myhost/$author
grep -B4 'homepage=' "$status_f" | head -n 4
read_valid_arg homepage "$homepage_rx" "无效的URL,请重新输入"
echo homepage=$homepage
#####################thrift_java_client#####################
[ -z "$thrift_java_client" ] && thrift_java_client=yes
grep -B4 'thrift_java_client=' "$status_f" | head -n 4
read_valid_arg thrift_java_client "$thrift_java_client_rx" "输入无效,请重新输入[y(es)|n(o)|true|false|on|off|0|1]" 
echo thrift_java_client=$thrift_java_client
#####################thrift_cpp_client#####################
[ -z "$thrift_cpp_client" ] && thrift_cpp_client=no
grep -B4 'thrift_cpp_client=' "$status_f" | head -n 4
read_valid_arg thrift_cpp_client "$thrift_cpp_client_rx" "输入无效,请重新输入[y(es)|n(o)|true|false|on|off|0|1]" 
echo thrift_cpp_client=$thrift_cpp_client

if istrue "$thrift_cpp_client"
then
    ###########################namespace_cpp##########################
    [ -z "$namespace_cpp" ] && namespace_cpp=${package/*./}
    grep -B8 'namespace_cpp=' "$status_f" | head -n 8
    read_valid_arg namespace_cpp "$namespace_cpp_rx" "无效的namespace,请重新输入"
    echo namespace_cpp=$namespace_cpp
fi

#######################service_restful_port#################
[ -z "$service_restful_port" ] && service_restful_port=8080
grep -B3 'service_restful_port=' "$status_f" | head -n 3
read_valid_arg service_restful_port "$service_restful_port_rx" "无效的端口号,请重新输入" 65535
echo service_restful_port=$service_restful_port
# 如果不需要thrft client,就不需要指定相关的端口
[ -z "$service_port" ] && service_port=28081
if istrue "$thrift_java_client" || istrue "$thrift_cpp_client"
then
###########################service_port#####################
grep -B3 'service_port=' "$status_f" | head -n 3
read_valid_arg service_port "$service_port_rx" "无效的端口号,请重新输入" 65535
echo service_port=$service_port
fi
########################service_xhr_port####################
[ -z "$service_xhr_port" ] && service_xhr_port=28082
grep -B3 'service_xhr_port=' "$status_f" | head -n 3
read_valid_arg service_xhr_port "$service_xhr_port_rx" "无效的端口号,请重新输入" 65535
echo service_xhr_port=$service_xhr_port
########################保存用户输入的参数##################
set_value "$status_f" projectname $projectname
set_value "$status_f" package     $package
set_value "$status_f" prj_package $prj_package
set_value "$status_f" service     $service
set_value "$status_f" groupid     $groupid
set_value "$status_f" artifactid  $artifactid
set_value "$status_f" version     $version
set_value "$status_f" prjurl      $prjurl
set_value "$status_f" scmurl      $scmurl
set_value "$status_f" author      $author
set_value "$status_f" email       $email
set_value "$status_f" homepage    $homepage
set_value "$status_f" thrift_java_client   $thrift_java_client
set_value "$status_f" thrift_cpp_client    $thrift_cpp_client
set_value "$status_f" namespace_cpp        $namespace_cpp
set_value "$status_f" service_restful_port $service_restful_port
set_value "$status_f" service_port         $service_port
set_value "$status_f" service_xhr_port     $service_xhr_port
echo "############################################################"
echo "#                     开始生成项目框架                     #"
echo "############################################################"
echo "##########remove target ####################################"
rm -fr target && mkdir target 2>/dev/null
pushd target 1>/dev/null || exit
echo "##########copy ../template to target #######################"
cp -r ../template . || exit
./template/clean_mprj.sh ./template || exit
## 排除可能会由编译工具生成的 target,bin,build 文件夹和日志文件
echo "##########delete 'target','bin','build' folder in template #"
find template \( -name target -o -name bin -o -name build \) -type d -exec  rm -fr "{}" \;  || exit
echo "##########delete *.log folder in template ##################"
find template \( -name *.log -o -name .DS_Store \) -type f -delete  || exit
echo "##########refactor file name ###############################"
## 文件路径改名,排除可能会由编译工具生成的 target,bin,build 文件夹和日志文件
find template -type f ! -path */target/* ! -path */bin/* ! -path */build/*  | while read fname; 
do 
    ## 替换文件路径中所有myorg,myrpc,Myrpc字符串生成新的路径
    _file=${fname/template/$projectname}
    ## 替换一级子文件夹名字
    _file=${_file/myrpc/$projectname}
    ## 替换java包名
    _file=${_file/myorg\/myrpc/${prj_package//.//}}
    _file=${_file//myrpc/${projectname//-/_}}
    _file=${_file//Myrpc/$service}
    ## echo rename $fname to $_file
    parent=$(dirname $_file)
    ## 为移动的文件创建所有父级文件夹
    [ -d "$parent" ] || mkdir -p $parent 1>/dev/null || exit
    mv $fname $_file || exit 
done  || exit

echo "##########delete clean empty folder in template ############"
find template -type d -empty -delete
[ ! -d template ] || echo "WARNNING:template folder is not empty" 
# 如果不需要thrft client,就不需要指定相关的项目
if ! istrue "$thrift_java_client"
then
    echo "##########REMOVE java client prject folder##################"
    rm -fr $projectname/$projectname-client || exit
    rm -fr $projectname/$projectname-client-base || exit
    rm -fr $projectname/$projectname-client-thrifty || exit
    echo "##########REMOVE java client prject form pom.xml############"
    sed "${sedi[@]}" "s|<module>myrpc-client</module>||g" "$projectname/pom.xml" || exit
    sed "${sedi[@]}" "s|<module>myrpc-client-base</module>||g" "$projectname/pom.xml" || exit
    sed "${sedi[@]}" "s|<module>myrpc-client-thrifty</module>||g" "$projectname/pom.xml" || exit
fi
# 如果不需要thrft c++ client,就不需要指定相关的项目
if ! istrue "$thrift_cpp_client"
then
    echo "##########REMOVE c++ client prject folder##################"
    rm -fr $projectname/$projectname-client-cpp || exit
    echo "##########REMOVE c++ client prject form pom.xml############"
    sed "${sedi[@]}" "s|<module>myrpc-client-cpp</module>||g" "$projectname/pom.xml" || exit
fi
if  notrue "$thrift_java_client" && notrue "$thrift_cpp_client"
then
    echo "##########Set server.start to false in defaultConfig.xml####"
    set_xml_value $projectname/$projectname-local/src/main/resources/defaultConfig.xml server.start false || exit
fi
echo "##########refact all pom.xml file ##########################"
find $projectname -name pom.xml -type f | while read fname;
do 
    [ "$prjurl"      = "https://myhost/myrpc"             ] || sed "${sedi[@]}" "s!<url>https://myhost/myrpc!<url>$prjurl!g"           "$fname"  || exit
    [ "$scmurl"      = "scm:git:https://myhost/myrpc.git" ] || sed "${sedi[@]}" "s!scm:git:https://myhost/myrpc.git!$scmurl!g"         "$fname"  || exit
    [ "$email"       = "unknow_author@myhost"             ] || sed "${sedi[@]}" "s!<email>unknow_author@myhost!<email>$email!g"        "$fname"  || exit
    [ "$homepage"    = "https://myhost/unknow_author"     ] || sed "${sedi[@]}" "s!<url>https://myhost/unknow_author!<url>$homepage!g" "$fname"  || exit
    [ "$author"      = "unknow_author"  ] || sed "${sedi[@]}" "s/unknow_author/$author/g"   "$fname"  || exit
    [ "$projectname" = "myrpc"          ] || sed "${sedi[@]}" -r "s/<(artifactId|name|module)>myrpc/<\\1>$projectname/g" "$fname"  || exit
    [ "$projectname" = "myrpc"          ] || sed "${sedi[@]}" -r "s/myrpc-/$projectname-/g" "$fname"  || exit
    [ "$groupid"     = "mygroupid"      ] || sed "${sedi[@]}" "s/mygroupid/$groupid/g"      "$fname"  || exit
    [ "$version"     = "0.0.0-SNAPSHOT" ] || sed "${sedi[@]}" "s/0.0.0-SNAPSHOT/$version/g" "$fname"  || exit
done || exit

echo "##########refact all source file ###########################"
find $projectname ! -name *.jpg ! -name *.png ! -name *.jar -type f | while read fname;
do
    echo refact $fname
    [ "$prjurl"      = "https://myhost/myrpc"             ] || sed "${sedi[@]}" "s!https://myhost/myrpc!$prjurl!g" "$fname" || exit
    [ "$email"       = "unknow_author@myhost"             ] || sed "${sedi[@]}" "s!unknow_author@myhost!$email!g"  "$fname"  || exit
    sed "${sedi[@]}" "s/myhost/$myhost/g" "$fname" || exit
    [ "$author"      = "unknow_author"  ] || sed "${sedi[@]}" "s/unknow_author/$author/g" "$fname" || exit
    # /myorg/myrpc/ 都是包名对应的路径
    [ "$prj_package" = "myorg.myrpc" ] || sed "${sedi[@]}"  "s!/myorg/myrpc/!/${prj_package/.//}/!g" "$fname" || exit
    # 替换包名
    #echo "repalce myorg.myrpc with $prj_package"
    [ "$prj_package" = "myorg.myrpc" ] || sed "${sedi[@]}" "s/myorg\.myrpc/$prj_package/g" "$fname" || exit
    #echo "repalce myorg_myrpc with $prj_package" for C++ preprocessor code
    [ "$prj_package" = "myorg.myrpc" ] || sed "${sedi[@]}" "s/myorg_myrpc/${prj_package//./_}/g" "$fname" || exit
    # myrpc- 都是文件夹名
    [ "$projectname" = "myrpc" ] || sed "${sedi[@]}" "s/myrpc-/$projectname-/g" "$fname" || exit
    # ".myrpc" /.myrpc 都是文件夹名
    [ "$projectname" = "myrpc" ] || sed "${sedi[@]}" -r "s!(/|\")\\.myrpc!\\1.$projectname!g" "$fname" || exit
    #echo "repalce myrpc with $projectname"
    [ "$projectname" = "myrpc" ] || sed "${sedi[@]}" "s/myrpc/${projectname//-/_}/g" "$fname" || exit
    # /myorg/ 都是包名对应的路径
    [ "$package"     = "myorg" ] || sed "${sedi[@]}"  "s!/myorg/!/${package/.//}/!g" "$fname" || exit
    PRJNAME=${projectname//-/_}
    ## 转大写的项目名称
    ## macOS下${PRJNAME^^}这种转大写的语法不支持
    PRJNAME=$(echo $PRJNAME | awk '{print toupper($0)}')
    #echo "repalce MYRPC with $PRJNAME"
    [ "$projectname" = "myrpc" ] || sed "${sedi[@]}" "s/MYRPC/$PRJNAME/g" "$fname" || exit
    if [[ "${fname##*.}" =~ cpp|cxx|tcc|cc|c\+\+|h|hpp|hh ]] ; 
    then
        # C++代码修改namespace
        echo "repace myorg with $namespace_cpp for namespace"
        [ "$package" = "myorg" ] || sed "${sedi[@]}"  "s/myorg/$namespace_cpp/g" "$fname" || exit
    else
        #echo "repalce myorg with $package"
        [ "$package" = "myorg" ] || sed "${sedi[@]}" "s/myorg/$package/g" "$fname" || exit
    fi
    #echo "repalce Myrpc with $service"
    [ "$service" = "Myrpc" ] || sed "${sedi[@]}" "s/Myrpc/$service/g" "$fname" || exit
    #echo "repalce 8080  with $service_restful_port"
    [ $service_restful_port -eq 8080 ] || sed "${sedi[@]}" "s/8080/$service_restful_port/g" "$fname" || exit
    #echo "repalce 28081 with $service_port"
    [ $service_port -eq 28081 ] || sed "${sedi[@]}" "s/28081/$service_port/g" "$fname" || exit
    #echo "repalce 28082 with $service_xhr_port"
    [ $service_xhr_port -eq 28082 ] || sed "${sedi[@]}" "s/28082/$service_xhr_port/g" "$fname" || exit
done || exit

############################################################
#    如果安装有maven,则编译重构的项目验证重构正确性        #
############################################################
pushd $projectname 1>/dev/null || exit  
which mvn 1>/dev/null && mvn clean compile || exit
echo "############################################################"
echo "#  $projectname 的框架源码生成完毕                          "
echo "#  保存在  target/$projectname                              "
echo "#  现在你可以在此基础上开始做自己的业务设计了              #"
echo "#  请继续阅读 README.md, 了解业务设计的相关说明            #"
echo "############################################################"
popd 1>/dev/null || exit
popd 1>/dev/null || exit
